package log

import (
    "fmt"
    "os"
    "strings"
    "time"
)

// assert interface compliance.
var _ Interface = (*Entry)(nil)

// Now returns the current time.
var Now = time.Now

// Entry represents a single log entry.
type Entry struct {
    Logger    *Logger   `json:"-"`
    Fields    Fields    `json:"fields"`
    Level     Level     `json:"level"`
    Timestamp time.Time `json:"timestamp"`
    Message   string    `json:"message"`
    start     time.Time
    fields    []Fields
}

// NewEntry returns a new entry for `log`.
func NewEntry(log *Logger) *Entry {
    return &Entry{
        Logger: log,
    }
}

// WithFields returns a new entry with `fields` set.
func (e *Entry) WithFields(fields Fielder) *Entry {
    f := []Fields{}
    f = append(f, e.fields...)
    f = append(f, fields.Fields())
    return &Entry{
        Logger: e.Logger,
        fields: f,
    }
}

// WithField returns a new entry with the `key` and `value` set.
func (e *Entry) WithField(key string, value interface{}) *Entry {
    return e.WithFields(Fields{key: value})
}

// WithDuration returns a new entry with the "duration" field set
// to the given duration in milliseconds.
func (e *Entry) WithDuration(d time.Duration) *Entry {
    return e.WithField("duration", d.Milliseconds())
}

// WithError returns a new entry with the "error" set to `err`.
//
// The given error may implement .Fielder, if it does the method
// will add all its `.Fields()` into the returned entry.
func (e *Entry) WithError(err error) *Entry {
    if err == nil {
        return e
    }

    ctx := e.WithField("error", err.Error())

    if s, ok := err.(stackTracer); ok {
        frame := s.StackTrace()[0]

        name := fmt.Sprintf("%n", frame)
        file := fmt.Sprintf("%+s", frame)
        line := fmt.Sprintf("%d", frame)

        parts := strings.Split(file, "\n\t")
        if len(parts) > 1 {
            file = parts[1]
        }

        ctx = ctx.WithField("source", fmt.Sprintf("%s: %s:%s", name, file, line))
    }

    if f, ok := err.(Fielder); ok {
        ctx = ctx.WithFields(f.Fields())
    }

    return ctx
}

// Debug level message.
func (e *Entry) Debug(msg string) {
    e.Logger.log(DebugLevel, e, msg)
}

// Info level message.
func (e *Entry) Info(msg string) {
    e.Logger.log(InfoLevel, e, msg)
}

// Warn level message.
func (e *Entry) Warn(msg string) {
    e.Logger.log(WarnLevel, e, msg)
}

// Error level message.
func (e *Entry) Error(msg string) {
    e.Logger.log(ErrorLevel, e, msg)
}

// Fatal level message, followed by an exit.
func (e *Entry) Fatal(msg string) {
    e.Logger.log(FatalLevel, e, msg)
    os.Exit(1)
}

// Debugf level formatted message.
func (e *Entry) Debugf(msg string, v ...interface{}) {
    e.Debug(fmt.Sprintf(msg, v...))
}

// Infof level formatted message.
func (e *Entry) Infof(msg string, v ...interface{}) {
    e.Info(fmt.Sprintf(msg, v...))
}

// Warnf level formatted message.
func (e *Entry) Warnf(msg string, v ...interface{}) {
    e.Warn(fmt.Sprintf(msg, v...))
}

// Errorf level formatted message.
func (e *Entry) Errorf(msg string, v ...interface{}) {
    e.Error(fmt.Sprintf(msg, v...))
}

// Fatalf level formatted message, followed by an exit.
func (e *Entry) Fatalf(msg string, v ...interface{}) {
    e.Fatal(fmt.Sprintf(msg, v...))
}

// Trace returns a new entry with a Stop method to fire off
// a corresponding completion log, useful with defer.
func (e *Entry) Trace(msg string) *Entry {
    e.Info(msg)
    v := e.WithFields(e.Fields)
    v.Message = msg
    v.start = time.Now()
    return v
}

// Stop should be used with Trace, to fire off the completion message. When
// an `err` is passed the "error" field is set, and the log level is error.
func (e *Entry) Stop(err *error) {
    if err == nil || *err == nil {
        e.WithDuration(time.Since(e.start)).Info(e.Message)
    } else {
        e.WithDuration(time.Since(e.start)).WithError(*err).Error(e.Message)
    }
}

// mergedFields returns the fields list collapsed into a single map.
func (e *Entry) mergedFields() Fields {
    f := Fields{}

    for _, fields := range e.fields {
        for k, v := range fields {
            f[k] = v
        }
    }

    return f
}

// finalize returns a copy of the Entry with Fields merged.
func (e *Entry) finalize(level Level, msg string) *Entry {
    return &Entry{
        Logger:    e.Logger,
        Fields:    e.mergedFields(),
        Level:     level,
        Message:   msg,
        Timestamp: Now(),
    }
}
